Mixpanel Agent

James Peret лет %!s(int64=7): %!d(string=назад)
Родитель
Сommit
72172f1b4e
2 измененных файлов с 178 добавлено и 0 удалено
  1. 2 0
      Gemfile
  2. 176 0
      app/models/agents/mixpanel_agent.rb

+ 2 - 0
Gemfile

@@ -121,6 +121,8 @@ gem 'string-scrub'	# for ruby <2.1
121 121
 gem 'therubyracer', '~> 0.12.2'
122 122
 gem 'typhoeus', '~> 0.6.3'
123 123
 gem 'uglifier', '~> 2.7.2'
124
+gem 'mixpanel_client'
125
+gem 'date'
124 126
 
125 127
 group :development do
126 128
   gem 'better_errors', '~> 1.1'

+ 176 - 0
app/models/agents/mixpanel_agent.rb

@@ -0,0 +1,176 @@
1
+require 'mixpanel_client'
2
+require 'date'
3
+
4
+module Agents
5
+  class MixpanelAgent < Agent
6
+    include WebRequestConcern
7
+
8
+    cannot_receive_events!
9
+    can_dry_run!
10
+    default_schedule "every_1d"
11
+
12
+    DEFAULT_EVENTS_ORDER = [['{{date_published}}', 'time'], ['{{last_updated}}', 'time']]
13
+
14
+    description do
15
+      <<-MD
16
+        The Mixpanel Agent checks for analytics data and returns an event.
17
+
18
+        # Ordering Events
19
+
20
+        #{description_events_order}
21
+
22
+        In this Agent, the default value for `events_order` is `#{DEFAULT_EVENTS_ORDER.to_json}`.
23
+      MD
24
+    end
25
+
26
+    def default_options
27
+      {
28
+        'event_name' => "Page Visit",
29
+        'property' => "Page",
30
+        'value' => "home",
31
+        'time' => 24,
32
+        'interval' => 'hour'
33
+      }
34
+    end
35
+
36
+    event_description <<-MD
37
+      Events look like:
38
+
39
+          {
40
+            "count": "45",
41
+            'event_name': "Page Visit",
42
+            'property': "Page",
43
+            'value': "home",
44
+            'time': 24,
45
+            'interval': 'hour'
46
+          }
47
+
48
+    MD
49
+
50
+    def working?
51
+
52
+    end
53
+
54
+    def validate_options
55
+      errors.add(:base, "event_name is required") unless options['event_name'].present?
56
+
57
+      unless options['property'].present? && options['value'].present?
58
+        errors.add(:base, "Please provide 'property' and 'value'")
59
+      end
60
+
61
+      validate_web_request_options!
62
+      validate_events_order
63
+    end
64
+
65
+    def events_order
66
+      super.presence || DEFAULT_EVENTS_ORDER
67
+    end
68
+
69
+    def check
70
+      create_event :payload => {
71
+        count: mixpanel_event_number(options),
72
+        event_name: options['event_name'],
73
+        property: options['property'],
74
+        value: options['value'],
75
+        time: options['time'],
76
+        interval: options['interval']
77
+      }
78
+    end
79
+
80
+    protected
81
+
82
+    def mixpanel_config
83
+        {
84
+          api_key: ENV['MIXPANEL_API_KEY'],
85
+          api_secret: ENV['MIXPANEL_SECRET_KEY']
86
+        }
87
+      end
88
+
89
+    def mixpanel_client
90
+      @mixpanel_client ||= Mixpanel::Client.new(mixpanel_config())
91
+    end
92
+
93
+    def mixpanel_event_number(options)
94
+      property, value = options[:property], options[:value]
95
+
96
+      unless (property && value) || (!property && !value)
97
+        raise "Must specify both 'property' and 'value' or none"
98
+      end
99
+
100
+      if [TrueClass, FalseClass].include?(value.class)
101
+        raise "As of Aug 7, 2013, MixPanel has a bug with querying boolean values\nPlease use number_for_event_using_export until that's fixed"
102
+      end
103
+
104
+      event_name = options[:event_name]
105
+
106
+      unless event_name
107
+        raise "Event name must be provided"
108
+      end
109
+
110
+      type = options[:type] || "general" #MixPanel API uses the term 'general' to mean 'total'
111
+
112
+      unless ["unique", "general", "average"].include? type
113
+        raise "Invalid type #{type}"
114
+      end
115
+
116
+      num_days = options[:time] || 24
117
+      interval = options[:interval] || "hour"
118
+
119
+      mixpanel_options = {
120
+        type: type,
121
+        unit: interval,
122
+        interval: num_days,
123
+        limit: 5,
124
+      }
125
+
126
+      if property && value
127
+        mixpanel_endpoint = "events/properties/"
128
+        mixpanel_options.merge!({
129
+          event: event_name,
130
+          values: [value],
131
+          name: property
132
+        })
133
+      else
134
+        mixpanel_endpoint = "events/"
135
+        mixpanel_options.merge!({
136
+          event: [event_name]
137
+        })
138
+      end
139
+
140
+      data = mixpanel_client.request(mixpanel_endpoint, mixpanel_options)
141
+
142
+      total_for_events(data)
143
+    end
144
+
145
+    def total_for_events(data)
146
+      counts_per_property = data["data"]["values"].collect do |c, values|
147
+        values.collect { |k, v| v }.inject(:+)
148
+      end
149
+
150
+      #now, calculate grand total
151
+      counts_per_property.inject(:+)
152
+    end
153
+
154
+    ###########################
155
+
156
+    def number_for_event_using_export(event_name, property, value, num_days = 30)
157
+
158
+      # TODO:
159
+      # MixPanel doesn't understand boolean values for properties
160
+      # There is an open ticket, but for now, there is a work around to use export API
161
+      # https://mixpanel.com/docs/api-documentation/exporting-raw-data-you-inserted-into-mixpanel
162
+      to_date = Date.today
163
+      from_date = to_date - num_days
164
+
165
+      data = mixpanel_client.request('export', {
166
+        event: [event_name],
167
+        from_date: from_date.to_s,
168
+        to_date: to_date.to_s,
169
+        where: "boolean(properties[\"#{property}\"]) == #{value} ",
170
+      })
171
+
172
+      data.count
173
+    end
174
+
175
+  end
176
+end